using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace asn_compile_cs
{

    enum ValueType { None, Integer, Boolean, List, Symbol, Double, Binary, SymbolList, 
        Null, Extension, PlusInfinity, MinusInfinity, String, SetOf,

        ValueFromObject, ValueSetFromObject, ObjectFromObject, ObjectSetFromObject,
        ValueSetFromObjects, ObjectSetFromObjects, ExtractFromObject, ExtractFromObjectSet
    };
 
    class Value
    {
        public static Value ValueNull = new Value(0, Type.Null);


        ValueType m_valType;

        long m_iValue;
        bool m_b;
        Binary m_bin;
        double m_dValue;
        Type m_type;
        String m_name;
        Symbol m_sym;
        SymbolList m_symList;
        TokenList m_tknlst;
        ValueList m_valList;

        Value()
        {
            m_valType = ValueType.None;
        }

        public Value(ValueType vt)
        {
            m_valType = vt;
            Debug.Assert(vt == ValueType.Extension);
        }

        public Value(long iVal, Type typ)
        {
            m_valType = ValueType.Integer;
            m_iValue = iVal;
            m_type = typ;
        }

        public Value(double val, Type typ)
        {
            m_valType = ValueType.Double;
            m_dValue = val;
            m_type = typ;
        }

        public Value(Binary bin)
        {
            m_valType = ValueType.Binary;
            m_bin = bin;
        }

        public Value(bool f)
        {
            m_valType = ValueType.Boolean;
            m_b = f;
            m_type = Type.BooleanType;
        }

        public Value(ValueList lst)
        {
            m_valType = ValueType.List;

            m_valList = lst;
            if (lst.type != null) {
                m_type = lst.type;
            }
        }

        public Value(String str)
        {
            m_valType = ValueType.String;
            m_name = str;
        }

        public Value(Symbol sym)
        {
            m_valType = ValueType.Symbol;

            Debug.Assert(sym.IsType(SymbolType.Object) || sym.IsType(SymbolType.Value) || sym.IsType(SymbolType.ObjectSet) ||
                         sym.IsType(SymbolType.ObjectParameter) || sym.IsType(SymbolType.ValueParameter) || sym.IsType(SymbolType.ObjectSetParameter) ||
                         sym.IsType(SymbolType.Value | SymbolType.InList));
            m_sym = sym;
            m_type = sym.type;
        }

        public Value(SymbolList symList)
        {
            m_valType = ValueType.SymbolList;
            m_symList = symList;
        }

        public Value(ValueList valList, ValueType vt)
        {
            m_valType = vt;
            m_valList = valList;
        }

        public Value(ValueType vt, Value val, Type type, TokenList tknlst)
        {
            Debug.Assert(vt == ValueType.ExtractFromObject || vt == ValueType.ExtractFromObjectSet);

            m_valType = vt;
            m_valList = new ValueList(val);
            m_type = type;
            m_tknlst = tknlst;
        }

        public Binary bin { get { Debug.Assert(m_valType == ValueType.Binary); return m_bin; } }

        public Boolean bVal
        {
            get { Debug.Assert(m_valType == ValueType.Boolean); return m_b; }
        }

        public long iVal
        {
            get { Debug.Assert(m_valType == ValueType.Integer);  return m_iValue; }
        }

        public String name
        {
            get { return m_name; }
            set { m_name = value; }
        }

        public Symbol sym
        {
            get { Debug.Assert(m_valType == ValueType.Symbol);  return m_sym; }
            set { m_sym = value; m_valType = ValueType.Symbol; }
        }

        public SymbolList symList
        {
            get { return m_symList; }
        }

        public TokenList tknlst { get { return m_tknlst; } }

        public Type type
        {
            get { return m_type; }
            set { m_type = value; }
        }

        public ValueList valueList
        {
            get { return m_valList; }
            set { m_valList = value; }
        }

        public ValueType valueType
        {
            get { return m_valType; }
        }

        public void ChangeTo(Value val)
        {
            m_valType = val.m_valType;
            m_iValue = val.m_iValue;
            m_b = val.m_b;
            m_bin = val.m_bin;
            m_dValue = val.m_dValue;
            m_type = val.m_type;
            m_name = val.m_name;
            m_sym = val.m_sym;
            m_symList = val.m_symList;
            m_tknlst = val.m_tknlst;
            m_valList = val.m_valList;
        }

        /// <summary>
        /// Collapse removes indirections from the system.
        /// </summary>
        public void Collapse()
        {
            switch (m_valType) {
            case ValueType.Symbol:
                m_sym.Collapse();
                if (m_sym.GetSymbolType() == SymbolType.ObjectSetParameter) {
                    break;
                }

                m_valType = m_sym.value.m_valType;
                switch (m_valType) {
                case ValueType.Boolean:
                    m_b = m_sym.value.bVal;
                    break;

                case ValueType.Integer:
                    m_iValue = m_sym.value.iVal;
                    break;

                case ValueType.List:
                    m_valList = m_sym.value.m_valList;
                    foreach (Value val in m_valList) {
                        val.Collapse();
                    }
                    break;

                }
                m_sym = null;
                break;

            case ValueType.List:
                foreach (Value val in m_valList) {
                    val.Collapse();
                }
                break;

            case ValueType.SymbolList:
                foreach (Symbol sym in m_symList) sym.Collapse();
                break;
            }
        }

        /// <summary>
        /// Dump displays the structure on the Console
        /// </summary>
        /// <param name="tab">Indentation level for the dump</param>
        public void Dump(String tab)
        {
            switch (m_valType) {
            case ValueType.Binary:
                Console.Error.WriteLine(tab + m_bin.ToString());
                break;

            case ValueType.Boolean:
                Console.Error.WriteLine(tab + m_b.ToString());
                break;

            case ValueType.Integer:
                Console.Error.WriteLine(tab + m_iValue.ToString());
                break;

            case ValueType.List:
                Console.Error.WriteLine(tab + "<");
                foreach (Value val in m_valList) {
                    val.Dump(tab + "    ");
                }
                Console.Error.WriteLine(tab + ">");
                break;

            case ValueType.SymbolList:
                Console.Error.WriteLine(tab + "<");
                m_symList.Dump(tab + "    ");
                Console.Error.WriteLine(tab + ">");
                break;

            case ValueType.Symbol:
                Console.Error.WriteLine(tab + m_sym.name);
                break;

            default:
                Console.Error.WriteLine(tab + m_valType.ToString());
                break;
            }
        }

        public override bool Equals(object obj)
        {
            // If parameter is null return false.
            if (obj == null) {
                return false;
            }

            // If parameter cannot be cast to Point return false.
            Value val = obj as Value;
            if ((System.Object)val == null) {
                return false;
            }

            //
            //  If the addresses match - good
            //

            if (this == val) return true;

            // Return true if the fields match:
            if (m_valType != val.m_valType) return false;

            switch (m_valType) {
            case ValueType.Integer:
                return m_iValue == val.m_iValue;

            case ValueType.SymbolList:
                if (m_symList.Count != val.m_symList.Count) return false;
                for (int i = 0; i < m_symList.Count; i++) {
                    if (!m_symList[i].Equals(val.m_symList[i])) return false;
                }
                return true;

            case ValueType.List:
                if (m_valList.Count != val.m_valList.Count) return false;
                for (int i = 0; i < m_valList.Count; i++) {
                    if (!m_valList[i].Equals(val.m_valList[i])) return false;
                }
                return true;

            case ValueType.SetOf:
                Debug.Assert(false, "NYI: Value.Equals.SetOf");
                return false;
            }

            Debug.Assert(false, "NYI: Value.Equals");
            return false;
        }

        /// <summary>
        /// Creates a new value object after replacing items from the parameter list
        /// </summary>
        /// <param name="paramList">list of parameters to be replaced</param>
        /// <returns>new value </returns>
        public Value Instance(SymbolList paramList)
        {
            //
            //  This is pointing to a symbol object.  Depending on what that symbol
            //  is we either need to clone the value on it or to replace it with a 
            //  parameter value.
            //

            if (m_sym != null) {
                switch (m_sym.GetSymbolType()) {
                case SymbolType.ValueParameter:
                    foreach (Symbol sym in paramList) {
                        if (sym.name == m_sym.name) {
                            return sym.value;
                        }
                    }
                    break;

                case SymbolType.ObjectSetParameter:
                    foreach (Symbol sym in paramList) {
                        if (sym.name == m_sym.name) {
                            return sym.value;
                        }
                    }
                    break;

                case SymbolType.ObjectParameter:
                    foreach (Symbol sym in paramList) {
                        if (sym.name == m_sym.name) {
                            return sym.value;
                        }
                    }
                    break;

                default:
                    // Collapse();
                    Debug.Assert(m_sym != null);
                    break;
                }
            }

            Value newVal = new Value();

            newVal.m_valType = m_valType;
            newVal.m_bin = m_bin;
            newVal.m_b = m_b;
            newVal.m_dValue = m_dValue;
            newVal.m_iValue = m_iValue;
            newVal.m_sym = m_sym;
            // M00QUEST - What about m_symList?
            
            //
            //  Deal with complex items
            //

            if (m_type != null) {
                newVal.m_type = m_type.Instance(paramList);
            }
            if (m_valList != null) {
                newVal.m_valList = m_valList.Instance(paramList);
            }
            return newVal;
        }

        /// <summary>
        /// ToString textizes the object
        /// </summary>
        /// <returns>text version of the string.</returns>
        public override string ToString()
        {
            return m_iValue.ToString();
        }
    }

    class ValueList : IEnumerable<Value>
    {
        List<Value> m_lst;
        Type m_type;

        public ValueList()
        {
            m_lst = new List<Value>();
        }

        public ValueList(Value val)
        {
            m_lst = new List<Value>();
            m_lst.Add(val);
        }

        public ValueList(List<Value> lst)
        {
            m_lst = lst;
        }

        public int Count { get { return m_lst.Count; } }

        public Type type
        {
            get { return m_type; }
            set { m_type = value; }
        }

        public Value this[int i]
        {
            get { return m_lst[i]; }
        }


        public void Add(Value val)
        {
            m_lst.Add(val);
        }

        public void Add(ValueList lst)
        {
            m_lst.AddRange(lst.m_lst);
        }

        public void Insert(int i, Value val)
        {
            m_lst.Insert(i, val);
        }

        /// <summary>
        /// Creates a new value object after replacing items from the parameter list
        /// </summary>
        /// <param name="paramList">list of parameters to be replaced</param>
        /// <returns>new value </returns>
        public ValueList Instance(SymbolList paramList)
        {
            ValueList lst = new ValueList();

            foreach (Value val in m_lst) {
                lst.Add(val.Instance(paramList));
            }

            return lst;
        }

        public void Remove(Value val)
        {
            m_lst.Remove(val);
        }

        public IEnumerator<Value> GetEnumerator()
        {
            return m_lst.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return m_lst.GetEnumerator();
        }

    }

    class Binary
    {
        int m_cbits;            // # of bits in the object - may not be a multiple of 8!!!!
        Byte[] m_rgb;           // Array of bytes - most significant bit is to the far left

        public Byte[] rgb { get { return m_rgb; } }
        public int cbits { get { return m_cbits; } }

        static public Binary Convert(String str)
        {
            Binary bin = new Binary();
            int cb;
            int cbit;
            int i;

            //
            //  Two different input types - either hex or binary based on the last character
            //

            if (str[str.Length - 1] == 'H') {
                //
                //  Start by over allocating the array
                //

                bin.m_rgb = new Byte[str.Length];
                cb = 0;
                cbit = 4;
                for (i = 1; i < str.Length - 2; i++) {
                    if (('0' <= str[i]) && (str[i] <= '9')) {
                        bin.m_rgb[cb] |= (byte) ((str[i] - '0') << cbit);
                    }
                    else if (('A' <= str[i]) && (str[i] <= 'F')) {
                        bin.m_rgb[cb] |= (byte)((str[i] - 'A'+10) << cbit);
                    }
                    else if (('a' <= str[i]) && (str[i] <= 'f')) {
                        bin.m_rgb[cb] |= (byte)((str[i] - 'a'+10) << cbit);
                    }
                    else {
                        continue;
                    }
                    if (cbit == 0) { cbit = 4; cb += 1; }
                    else cbit = 0;
                }

                if (cbit == 0) { cb += 1; }
                Array.Resize<Byte>(ref bin.m_rgb, cb);
                if (cbit == 0) bin.m_cbits = cb * 8 - 4;
                else bin.m_cbits = cb * 8;
            }
            else {
                //
                //  Start by over allocating the array
                //

                bin.m_rgb = new Byte[str.Length / 8 + 1];
                cb = 0;
                cbit = 7;
                for (i = 1; i < str.Length-2; i++) {
                    if (str[i] == '0') {
                        cbit -= 1;
                    }
                    else if (str[i] == '1') {
                        bin.m_rgb[cb] |= (byte) (1 << cbit);
                        cbit -= 1;
                    }
                    if (cbit == -1) {
                        cbit = 7;
                        cb += 1;
                    }
                }

                bin.m_cbits = cb * 8 + (7 - cbit);
                if (cbit != 7) cb += 1;

                Array.Resize<Byte>(ref bin.m_rgb, cb);
            }
            return bin;
        }

        public void SetBit(int iBit)
        {
            int cb = iBit / 8;
            int cbit = 7 - (iBit % 8);

            if ((m_rgb == null) || (cb + 1 > m_rgb.Length)) {
                Array.Resize<Byte>(ref m_rgb, cb + 1);
            }

            m_rgb[cb] |= (byte) (1 << cbit);
        }

        public override string ToString()
        {
            String str = "'";

            if (m_rgb != null) foreach (Byte b in m_rgb) str += b.ToString("X2");
            str += "'H";
            return str;
        }
    }
}
